#pragma once

#include <iostream>
#include <queue>
#include <functional>
#include <cstdlib>
#include <pthread.h>

#define THREADNUM_ERR 1
#define THREAD_CREAT_ERR 2
#define LOCK_ERR 3
#define UNLOCK_ERR 4
#define COND_WAIT_ERR 5
#define COND_SIGNAL_ERR 6

const size_t ThreadNum = 5; // Default number of threads

template <typename T>
class ThreadPool
{
public:
    ThreadPool(size_t threadnum = ThreadNum) : threadnum_(threadnum), IsStart(false)
    {
        if (threadnum_ < 0)
        {
            std::cout << "The number of threads in the thread pool is less than 1" << std::endl;
            exit(THREADNUM_ERR);
        }
        pthread_mutex_init(&mtx_, nullptr);
        pthread_cond_init(&con_, nullptr);
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&mtx_);
        pthread_cond_destroy(&con_);
    }

    void StartMultiThread()
    {
        if (IsStart == false)
        {
            for (int i = 0; i < threadnum_; ++i)
            {
                pthread_t tid;
                if (pthread_create(&tid, nullptr, ThreadHandler, this) != 0)
                {
                    std::cout << "Thread creation exception" << std::endl;
                    exit(THREAD_CREAT_ERR);
                }
            }
            IsStart = true;
        }
        else
        {
            std::cout << "Multiple threads have already started, and this function cannot be called repeatedly"
                      << std::endl;
            return;
        }
    }

    void PushTask(const T &val)
    {
        Lock();
        threadpool_.push(val);
        WakeUpOneCond();
        Unlock();
    }

private:
    T PopTask()
    {
        T temp = threadpool_.front();
        threadpool_.pop();
        return temp;
    }

public:
    static void *ThreadHandler(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *const _this = static_cast<ThreadPool<T> *const>(args);

        while (true)
        {
            _this->Lock();
            while (_this->Empty())
            {
                _this->SetCond();
            }
            T task = _this->PopTask();
            _this->Unlock();
            // Convention: Task has a functor -- Concurrent processing tasks
            task(pthread_self());
        }
        return nullptr;
    }

    // --------------------------------- Singleton pattern ---------------------------------
public:
    static ThreadPool<T>* GetThreadPool(size_t threadnum)
    {
        ThreadPool<T>* tp;
        if (tp != nullptr)
        {
            pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
            pthread_mutex_lock(&mtx);
            if (tp != nullptr)
            {
                tp = new ThreadPool(threadnum);
            }
            pthread_mutex_unlock(&mtx);
        }
        return tp;
    }

    // --------------------------------- queue、mutex、cond Api Encapsulation ---------------------------------

    bool Empty() { return threadpool_.empty(); } // null return 1, !null return 0

    void Lock()
    {
        if (pthread_mutex_lock(&mtx_) != 0)
        {
            std::cout << "lock error" << std::endl;
            exit(LOCK_ERR);
        }
    }

    void Unlock()
    {
        if (pthread_mutex_unlock(&mtx_) != 0)
        {
            std::cout << "lock error" << std::endl;
            exit(UNLOCK_ERR);
        }
    }

    void SetCond()
    {
        if (pthread_cond_wait(&con_, &mtx_) != 0)
        {
            std::cout << "Failed to set condition variable" << std::endl;
            exit(COND_WAIT_ERR);
        }
    }

    void WakeUpOneCond()
    {
        if (pthread_cond_signal(&con_) != 0)
        {
            std::cout << "Failed to wake up a single mutex waiting under condition variable" << std::endl;
            exit(COND_SIGNAL_ERR);
        }
    }

private:
    std::queue<T> threadpool_;
    pthread_t tid_;
    pthread_mutex_t mtx_;
    pthread_cond_t con_;
    size_t threadnum_;
    bool IsStart;
};